import os
import queue
import threading
import time

import cv2

from algorithm import fisheye_unfold
from apps.device.models import Camera
from cv_pig_django import settings


class CameraException(Exception):
    message = None

    # 初始化异常
    def __init__(self, message: str):
        # 初始化异常，定位异常信息描述
        self.message = message

    def __str__(self):
        return self.message


class BaseCamera:
    # 相机操作对象
    cam = None
    # 保存每一帧从rtsp流中读取到的画面，使用opencv读取，为BGR图片
    queue_image = queue.Queue(maxsize=10)
    # 后台取帧线程
    thread = None
    # 相机Model
    camera = None
    # 内参标定保存图片
    inner_cameras = []
    # 内参图片计时器
    inner_cameras_timer = time.time()

    # 相机基类
    def __init__(self, camera: Camera = None, camera_api=None):
        """
        使用rtsp流初始化相机参数
        rtsp格式：rtsp://[username]:[password]@[ip]:[port]/[codec]/[channel]/[subtype]/av_stream
        username: 用户名。例如admin。
        password: 密码。例如12345。
        ip: 为设备IP。例如 192.0.0.64。
        port: 端口号默认为554，若为默认可不填写。
        codec：有h264、MPEG-4、mpeg4这几种。
        channel: 通道号，起始为1。例如通道1，则为ch1。
        subtype: 码流类型，主码流为main，辅码流为sub。
        """
        if camera is not None:
            # 初始化相机参数
            self.camera = camera
            # 视频接口
            camera_api = camera.camera_api
            # 为None，相机中不存在api接口，
            if camera_api is None:
                # 判断相机的用户等参数是否存在，若存在，自动生成rtsp接口
                if (camera.camera_ip is None or camera.camera_ip == ''
                        or camera.camera_port is None or camera.camera_port == ''
                        or camera.camera_username is None or camera.camera_username == ''
                        or camera.camera_password is None or camera.camera_password == ''):
                    raise CameraException("相机参数异常")
                else:
                    camera_api = f'rtsp://{camera.camera_username}:{camera.camera_password}' \
                                 f'@{camera.camera_ip}:{camera.camera_port}/h264/ch1/main/av_stream '
            # ToDo
            # self.cam = cv2.VideoCapture(camera_api)
            self.cam = cv2.VideoCapture(0)
            if self.cam.isOpened():
                # 相机打开成功,启动线程读取数据
                self.thread = threading.Thread(target=self._thread, daemon=True)
                self.thread.start()
            else:
                # 打开失败，相机流错误
                raise CameraException("视频流接口访问失败")
        elif camera_api is not None:
            # ToDO
            # self.cam = cv2.VideoCapture(camera_api)
            self.cam = cv2.VideoCapture(0)
            if self.cam.isOpened():
                # 相机打开成功,启动线程读取数据
                self.thread = threading.Thread(target=self._thread, daemon=True)
                self.thread.start()
            else:
                # 打开失败，相机流错误
                raise CameraException("视频流接口访问失败")
        else:
            raise CameraException("参数异常")

    def _thread(self):
        """
        相机后台进程，持续读取相机
        opencv读取时会将信息存储到缓存区里，处理速度小于缓存区速度，会导致资源积累
        """
        # 线程一直读取视频流，将最新的视频流存在队列中
        while self.cam.isOpened():
            ret, img = self.cam.read()
            if not ret or img is None:
                # 读取相机失败
                pass
            else:
                # 读取内容成功，将数据存放在缓存区
                if self.queue_image.full():
                    # 队列满，队头出队
                    self.queue_image.get()
                    # 队尾添加数据
                    self.queue_image.put(img)
                else:
                    # 队尾添加数据
                    self.queue_image.put(img)

    # 直接读取图片
    def read(self):
        """
        直接读取从rtsp流中获取到的图片，不进行额外加工
        可能为空，需做判空处理
        """
        return self.queue_image.get()

    # 读取视频帧
    def get_frame(self):
        img = self.queue_image.get()
        if img is None:
            return None
        else:
            # 压缩图片，否则图片过大，编码效率慢，视频延迟过高
            img = cv2.resize(img, (0, 0), fx=0.25, fy=0.25)
            # 对图片进行编码
            ret, jpeg = cv2.imencode('.jpeg', img)
            return jpeg.tobytes()

    # 读取鱼眼展开视频帧
    def get_fisheye_frame(self, theta, fi, fov):
        img = self.queue_image.get()
        if img is None:
            return None
        else:
            # 鱼眼图片展开
            # 判断鱼眼内参是否已标定
            R = fisheye_unfold.extract_fisheye_valid_region(img)
            if self.camera.camera_model.check_camera_inner():
                # 相机已标定
                img = fisheye_unfold.project_unfold_fast(400, 400, self.camera.camera_model.get_mtx(),
                                                         self.camera.camera_model.get_dist(), theta, fi, fov, img, R)
            else:
                img = fisheye_unfold.project_unfold_fast_without_inner(400, 400, theta, fi, fov, img, R)
            # 对图片进行编码
            ret, jpeg = cv2.imencode('.jpeg', img)
            return jpeg.tobytes()

    # 内参标定截图
    def inner_screenshot(self):
        now = time.time()
        if (now - self.inner_cameras_timer) >= (60 * 5):
            # 更新计时器
            self.inner_cameras_timer = now
            # 截图时间超过5分钟，超时，重置截图区域
            self.inner_reset()
        else:
            # 更新计时器
            self.inner_cameras_timer = now
            # 读取图片
            img = self.queue_image.get()
            # 加入内参标定数组中
            if img is None:
                return None
            else:
                self.inner_cameras.append(img)
                suffix = 'calibrate/' + str(time.time()) + '.jpg'
                path = os.path.join(settings.MEDIA_ROOT, suffix)
                cv2.imencode('.jpg', img)[1].tofile(path)
                return 'media/' + suffix

    # 重置截图区域
    def inner_reset(self):
        self.inner_cameras.clear()


class CameraFactory:
    """
    相机工厂
    """
    # 存储实例化的所有相机
    cameras = {}
    # 临时相机，以api作为key，以[BaseCamera, time.time()]作为value，
    temp_cameras = {}

    def __init__(self):
        # 初始化时，实例所有相机
        pass

    @classmethod
    def get_camera(cls, camera_id: int):
        # 通过相机id获取相机
        camera = cls.cameras.get(camera_id)
        if camera is None:
            # 查看是否存在相机，存在访问
            try:
                camera = Camera.objects.get(id=camera_id)
                base_camera = BaseCamera(camera=camera)
                if base_camera is not None:
                    cls.cameras.setdefault(camera_id, base_camera)
                    return cls.cameras.get(camera_id)
                else:
                    return None
            except Camera.DoesNotExist:
                # 相机不存在
                return None
            except CameraException:
                # 相机实例失败
                return None
        else:
            return camera

    @classmethod
    def clean_overtime_temp_camera(cls):
        now = time.time()
        # 存储需要删除的key键
        del_key = []
        for key, value in cls.temp_cameras.items():
            # 临时相机时间超过5分钟，超时
            if (now - value[1]) >= (60 * 10):
                del_key.append(key)

        for key in del_key:
            del cls.temp_cameras[key]

    @classmethod
    def get_temp_camera(cls, camera_api):
        cls.clean_overtime_temp_camera()
        camera = cls.temp_cameras.get(camera_api)
        if camera is None:
            try:
                base_camera = BaseCamera(camera_api=camera_api)
                if base_camera is not None:
                    value = [base_camera, time.time()]
                    cls.temp_cameras.setdefault(camera_api, value)
                    return cls.temp_cameras.get(camera_api)[0]
                else:
                    return None
            except CameraException:
                # 相机实例失败
                return None
        else:
            return camera[0]


camera_factory = CameraFactory()
